package cn.edu.fudan.bdke.ncov.qa;

import cn.edu.fudan.bdke.ncov.qa.dao.Neo4jGraphDao;
import cn.edu.fudan.bdke.ncov.qa.model.AbsLogicForm;
import cn.edu.fudan.bdke.ncov.qa.model.LogicFormString;
import cn.edu.fudan.bdke.ncov.qa.util.ChineseNlp;
import cn.edu.fudan.bdke.ncov.qa.util.DictUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 基于模板的形式构建问答系统
 * 1.0系统基于假设问句中只包含单一实体和单一属性
 *
 * @author xym
 * @since 1.0
 */
public class TemplateBasedQaBuilder implements AbsQaBuilder {
    private static final Logger log = LoggerFactory.getLogger(TemplateBasedQaBuilder.class);

    private static List<String> entityDict; //知识库中的所有实体
    private static Map<String, List<String>> attributeMap;
    private static final String entityDictPath = "dict/entity-dict.txt";
    private static final String attributeDictPath = "dict/attr-dict.txt";

    @Override
    public AbsLogicForm naturalLanguage2LogicForm(String question) {
        question = question.trim();
        List<String> foundEntities = entityLinking(question);
        log.info("匹配的实体有{}", foundEntities);
        Map<String, String> foundAttributes = predicateMapping(question);
        log.info("匹配的属性有{}", foundAttributes);
        String lfQuery = "";
        if (foundEntities.size() != 0) {
            if (foundAttributes.size() == 0) {
                lfQuery = foundEntities.get(0);
            } else {
                List<String> attrTemp = new ArrayList<>(foundAttributes.keySet());
                if (attrTemp.size() == 1) {
                    lfQuery = String.format("%s:%s", foundEntities.get(0), foundAttributes.get(attrTemp.get(0)));
                }
            }
        }
        return new LogicFormString(lfQuery);
    }

    @Override
    public String logicForm2Query(AbsLogicForm logicForm) {
        String lfQuery = ((LogicFormString) logicForm).getLfQuery();
        String[] parts = lfQuery.split(":");
        if (parts.length > 1) {
            return String.format("MATCH (e)-[:%s]-(t) WHERE e.name=\"%s\" RETURN t", parts[1], parts[0]);
        } else {
            return String.format("MATCH (t) WHERE t.name =\"%s\" RETURN t", parts[0]);
        }
    }

    @Override
    public List<String> searchResult(String query) {
        Neo4jGraphDao graphDao = new Neo4jGraphDao();
        return graphDao.execute(query);
    }

    static {
        entityDict = DictUtil.load(entityDictPath);
        attributeMap = DictUtil.loadByLine(attributeDictPath, true);
    }


    /**
     * 找出问句中是否含有知识库中的实体，这里采用了字典匹配，可以使用检索代替
     * FIXME：暂时假设问句是简单问题，只包括单个实体
     */
    private List<String> entityLinking(String question) {
        String[] parts = question.split("[的是有]");
        List<String> ans = new ArrayList<>();
        for (String part : parts) {
            List<String> pp = ChineseNlp.segmentWord(part);
            if (pp.size() != 0) {
                for (String phrase : ngramWordGenerator(pp)) {
                    if (entityDict.contains(phrase)) {
                        ans.add(phrase);
                        return ans;
                    }
                }
            }

        }
        return ans;
    }

    /**
     * 找出问句中是否含有知识库中的属性，这里采用了同义词典的方法。
     * FIXME：暂时假设问句是简单问题，只包括单个属性
     */
    private Map<String, String> predicateMapping(String question) {
        String[] parts = question.split("[的是有]");
        LinkedHashMap<String, String> ans = new LinkedHashMap<>();
        for (String part : parts) {
            for (String attribute : attributeMap.keySet()) {
                List<String> synonyms = attributeMap.get(attribute);
                for (String synonym : synonyms) {
                    if (part.contains(synonym)) {
                        //synonym是问句中的属性表达，attribute是知识库中属性表达
                        ans.put(synonym, attribute);
                        return ans;
                    }
                }
            }
        }
        return ans;
    }

    /**
     * 根据ngram，组合词语
     *
     * @param wordList 词语序列
     * @return 所有的ngram词语
     */
    private List<String> ngramWordGenerator(List<String> wordList) {
        List<String> newWordList = new ArrayList<>();
        int len = wordList.size();
        // 控制ngram中n的大小
        for (int n = 1; n <= len; n++) {
            // j为依次遍历单词序列
            for (int j = 0; j <= len - n; j++) {
                StringBuilder sb = new StringBuilder();
                // 拼接由n个单词组成的新单词
                for (int k = j; k < j + n; k++) {
                    sb.append(wordList.get(k));
                }
                newWordList.add(sb.toString());
            }

        }
        return newWordList;
    }
}
